﻿using System;
using System.Linq;
using System.ComponentModel.Composition;
using Cubicle.Core.Interfaces;
using Cubicle.Core.Processors.Parameter;

namespace Cubicle.Core.Processors.Invoke
{
    [Export("Cubicle.Processors.Invoke.GenericClassProcessor", typeof(IProcessor<InstructionRequest>))]
    [ExportMetadata("Name", "GenericClassProcessor")]
    [ExportMetadata("Description", "Turns a generic target into a concrete type")]
    public class GenericClassProcessor : InstructionRequestProcessor
    {
        [Import]
        private ParameterProcessor _paramProcessor;

        [ImportingConstructor]
        public GenericClassProcessor() : base(RequestType.AllNonSpecial) {}

        #region Overrides of Processor

        public override bool Condition(InstructionRequest request)
        {
            if (request.Type() == RequestType.CustomConstruct) return false;
            var inst = request.Inst;
            return (inst.ClassArgs != null && inst.ClassArgs.Length > 0) 
                || request.TargetType.IsGenericType;
        }

        public override InstructionRequest ConditionalProcess(InstructionRequest request)
        {
            var targetType = request.TargetType;
            var inst = request.Inst;

            // Check if generic arguments are required but not supplied
            var sanityCheck = inst.ClassArgs == null || inst.ClassArgs.Length == 0;
            if (sanityCheck)
            {
                request.Fail("Generic class arguments required but not supplied. InstructionId={0}", request.Id);
                return request;
            }

            // Check if generic arguments were supplied but not required
            sanityCheck = !request.TargetType.IsGenericType;
            if (sanityCheck)
            {
                request.Fail("Generic class arguments supplied but not required. InstructionId={0}", request.Id);
                return request;
            }

            // Check if supply count and expected counts match
            var type = typeof (Type);
            var classArgs = inst.ClassArgs;
            var suppliedCount = classArgs.Length;
            var expectedArgs = targetType.GetGenericArguments();
            var requiredCount = expectedArgs.Length;
            sanityCheck = requiredCount != suppliedCount;
            if (sanityCheck)
            {
                request.Fail("Class arguments supplied = {0}. Class arguments required = {1}. InstructionId={2}",
                             suppliedCount, requiredCount, request.Id);
                return request;
            }

            // Convert parameters
            var results = classArgs
                .Select(x => new ParameterRequest(type, x))
                .Select(x => _paramProcessor.Process(x).Result as Type)
                .ToArray();

            request.TargetType = targetType.MakeGenericType(results);
            return request;
        }

        #endregion
    }
}